package historyElemente;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.io.IOException;
import java.util.Arrays;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * Modelliert Emoticons und bietet die Moeglichkeit, diese zu zeichnen.
 * 
 * @author Kilian Telschig
 * 
 */
public enum Emoticon implements HistoryElement {

	/*
	 * hab mich da ganz an die Tabelle aus wikipedia gehalten^^
	 */
	/*
	 * TODO to be continued
	 */

	/**
	 * Laechelndes Gesicht. Folgende Zeichenketten werden dadurch
	 * repraesentiert:<br>
	 * <blockquote>:)<br>
	 * :-)<br>
	 * =)<br>
	 * :]<br>
	 * :><br>
	 * :c)<br>
	 * x)<br>
	 * :o)</blockquote>
	 */
	NORMALES_SMILEY("/images/smiley.png", ":)", ":-)", "=)", ":]", ":>", ":c)",
			"x)", ":o)"),

	/**
	 * Zwinkerndes Gesicht. Folgende Zeichenketten werden dadurch
	 * repraesentiert:<br>
	 * <blockquote> ;)<br>
	 * ;-)<br>
	 * ;]<br>
	 * ;o)<br>
	 * </blockquote>
	 */
	ZWINKERNDES_SMILEY("/images/zwinkersmiley.png", ";)", ";-)", ";)", ";]",
			";o)"),

	/**
	 * Grinsendes Gesicht. Folgende Zeichenketten werden dadurch repraesentiert:<br>
	 * <blockquote>:D <br>
	 * ;D<br>
	 * :-D<br>
	 * =D<br>
	 * xD<br>
	 * XD<br>
	 * :oD </blockquote>
	 */
	LOL_SMILEY("/images/lolsmiley.png", ":D", ";D", ":-D", "=D", "xD", "XD",
			":oD"),

	/**
	 * Trauriges Gesicht. Folgende Zeichenketten werden dadurch repraesentiert:<br>
	 * <blockquote>:(<br>
	 * :-(<br>
	 * =(<br>
	 * :[<br>
	 * :<<br>
	 * :/<br>
	 * x(<br>
	 * :o(<br>
	 * :C</blockquote>
	 */
	TRAURIGES_SMILEY("/images/traurigessmiley.png", ":(", ":-(", "=(", ":[",
			":<", ":/", "x(", ":o(", ":C");

	/**
	 * Image dieses Emoticons.
	 */
	private Image icon;
	/**
	 * Zeichenketten-Repraesentationen dieses Emoticons.
	 */
	private char[][] matchendeZeichenketten;

	/**
	 * Image, das bei einem Ladefehler verwendet wird.
	 */
	private static Image notfallImage = new JFrame().getIconImage();

	/**
	 * Erstellt ein Emoticon.
	 * 
	 * @param path
	 *            Pfad zum Image
	 * @param zeichenketten
	 *            String-Repraesentationen der Emoticons
	 */
	private Emoticon(String path, String... zeichenketten) {
		this.icon = getImage(path);

		matchendeZeichenketten = new char[zeichenketten.length][];
		for (int i = 0; i < zeichenketten.length; i++) {
			matchendeZeichenketten[i] = zeichenketten[i].toCharArray();
		}
	}

	/**
	 * Laedt ein Images. Falls kein Bild vom gegebenen Pfad gefunden werden
	 * kann, wird das standard JFrame-IconImage verwendet (zum debuggen, sollte
	 * spaeter eh nicht mehr auftreten.
	 * 
	 * @param path
	 *            Pfad zum Image
	 * @return geladenes Image oder das JFrame-IconImage bei Ladefehler
	 */
	private static Image getImage(String path) {
		Image img = null;
		try {
			img = ImageIO.read(Emoticon.class.getResource(path));
		} catch (IOException e) {
			System.out.println(path);
			img = notfallImage;
		}
		return img;
	}

	/**
	 * Prueft, ob eine Zeichenkette durch das gegebene Emoticon repraesentiert
	 * wird.
	 * 
	 * @param e
	 *            fragliches Emoticon
	 * @param zeichenkette
	 *            gegebene Zeichenkette
	 * @return true, wenn die Zeichenkette das Emoticon repraesentiert, sonst
	 *         false
	 */
	private static boolean matches(Emoticon e, char[] zeichenkette) {
		if (e == null || zeichenkette == null) {
			return false;
		}
		for (char[] cs : e.matchendeZeichenketten) {
			if (Arrays.equals(cs, zeichenkette)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Liest einen String aus und gibt eines der Emoticons zurueck. Falls dieser
	 * Zeichenkette kein Emoticon zugeordnet ist, wird null zurueckgegeben.
	 * 
	 * @param s
	 *            auszulesender String
	 * @return entsprechendes Emoticon oder null, falls nichts gematcht
	 */
	public static Emoticon toEmoticon(String s) {
		return toEmoticon(s.toCharArray());
	}

	/**
	 * Liest eine Zeichenkette aus und gibt eines der Emoticons zurueck. Falls
	 * dieser Zeichenkette kein Emoticon zugeordnet ist, wird null
	 * zurueckgegeben.
	 * 
	 * @param cs
	 *            auszulesende Zeichenkette
	 * @return entsprechendes Emoticon oder null, falls nichts gematcht
	 */
	public static Emoticon toEmoticon(CharSequence cs) {
		return toEmoticon(cs.toString().toCharArray());
	}

	/**
	 * Liest ein Char-Array aus und gibt eines der Emoticons zurueck. Falls
	 * dieser Zeichenkette kein Emoticon zugeordnet ist, wird null
	 * zurueckgegeben.
	 * 
	 * @param array
	 *            auszulesendes Char-Array
	 * @return entsprechendes Emoticon oder null, falls nichts gematcht
	 */
	public static Emoticon toEmoticon(char[] array) {
		for (Emoticon e : values()) {
			if (matches(e, array)) {
				return e;
			}
		}
		return null;
	}

	/**
	 * Ermittelt, ob und wenn ja wo ein Emoticon in einem String vorkommt. Es
	 * wird bei vorkommen mehrerer Emoticons lediglich das erste gefundene
	 * angegeben.<br>
	 * <br>
	 * <strong>Hinweis:</strong><br>
	 * Die Laufzeit laesst vmtl. zu wuenschen uebrig, daher sollte diese Methode
	 * optimalerweise nur zum einmaligen Rendern eines Strings verwendet,
	 * werden.
	 * 
	 * @param s
	 *            ein String
	 * @return einEmoSubstringReturnType mit offset und Anzahl der Zeichen.
	 *         falls kein Emoticon gefunden, wird null geliefert.
	 */
	public static EmoSubstringReturnType containsEmoticon(String s) {
		return containsEmoticon(s.toCharArray());
	}

	/**
	 * Ermittelt, ob und wenn ja wo ein Emoticon in einer Zeichenkette vorkommt.
	 * Es wird bei vorkommen mehrerer Emoticons lediglich das erste gefundene
	 * angegeben.<br>
	 * <br>
	 * <strong>Hinweis:</strong><br>
	 * Die Laufzeit laesst vmtl. zu wuenschen uebrig, daher sollte diese Methode
	 * optimalerweise nur zum einmaligen Rendern eines Strings verwendet,
	 * werden.
	 * 
	 * @param cs
	 *            Zeichenkette
	 * @return einEmoSubstringReturnType mit offset und Anzahl der Zeichen.
	 *         falls kein Emoticon gefunden, wird null geliefert.
	 */
	public static EmoSubstringReturnType containsEmoticon(CharSequence cs) {
		return containsEmoticon(cs.toString().toCharArray());
	}

	/**
	 * Ermittelt, ob und wenn ja wo ein Emoticon in einer Zeichenkette vorkommt.
	 * Es wird bei vorkommen mehrerer Emoticons lediglich das erste gefundene
	 * angegeben.<br>
	 * <br>
	 * <strong>Hinweis:</strong><br>
	 * Die Laufzeit laesst vmtl. zu wuenschen uebrig, daher sollte diese Methode
	 * optimalerweise nur zum einmaligen Rendern einer Zeichenkette verwendet,
	 * werden.
	 * 
	 * @param array
	 *            Zeichenkette
	 * @return einEmoSubstringReturnType mit offset und Anzahl der Zeichen.
	 *         falls kein Emoticon gefunden, wird null geliefert.
	 */
	public static EmoSubstringReturnType containsEmoticon(char[] array) {

		uebersArrayLaufen: for (int i = 0; i < array.length - 1; i++) {
			final int offset = i;

			// auf 2er bzw 3er Emos testen
			for (int j = 2; j <= 3; j++) {
				final int length = j;

				if (array.length - offset < length) {
					// zu wenig zeichen fuern emo -> abbruch
					break uebersArrayLaufen;
				}

				char[] moeglichesEmo = new char[length];
				for (int k = 0; k < moeglichesEmo.length; k++) {
					moeglichesEmo[k] = array[offset + k];
				}

				// fuer alle Emoticon-Instanzen
				for (Emoticon emo : values()) {
					if (matches(emo, moeglichesEmo)) {
						return new EmoSubstringReturnType(offset, length);
					}
				}
			}
		}
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void drawElement(Graphics g, int x, int y) {
		/*
		 * Zeichnet das Emoticon quadratisch mit Kantenlaenge == Zeilenhoehe.
		 */

		int a = getWidth(g);

		// folgende Zeile auskommentieren, falls y die baseline der Zeile angibt
		// y -= a + g.getFontMetrics().getDescent();

		g.drawImage(icon, x, y, a, a, null);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getWidth(Graphics g) {
		/*
		 * Emoticons sollten in einem Quadrat gezeichnet werden, dessen
		 * Kantenlaenge der Zeilenhoehe entspricht.
		 */
		return g.getFontMetrics().getHeight();
	}

	/**
	 * Gibt einen entsprechenden Smiley als String zurueck.
	 * 
	 * @return Smiley als String
	 */
	@Override
	public String toString() {
		String s = "";
		if (matchendeZeichenketten.length > 0) {
			for (char c : matchendeZeichenketten[0]) {
				s += c;
			}
		} else {
			s = super.toString();
		}

		return s;
	}

	/**
	 * Container fuer einen Offset und eine Laenge.
	 * 
	 * @author Kilian Telschig
	 * 
	 */
	public static class EmoSubstringReturnType {
		/**
		 * Der Offset.
		 */
		public int offset;
		/**
		 * Die Laenge.
		 */
		public int length;

		/**
		 * Erstellt ein {@link EmoSubstringReturnType}.
		 * 
		 * @param offset
		 *            der offset
		 * @param length
		 *            die laenge
		 */
		public EmoSubstringReturnType(int offset, int length) {
			this.offset = offset;
			this.length = length;
		}
	}

	/**
	 * Unit Test fuer dieses Enum.
	 * 
	 * @param args
	 *            koane args
	 */
	@SuppressWarnings("serial")
	public static void main(String[] args) {
		// toString test
		for (Emoticon emo : values()) {
			System.out.println(emo.toString() + " (" + emo.name() + ")");
		}

		/*
		 * test containsEmoticon und toEmoticon
		 */
		String s = "-)shizzle^^.-;-)!!!";
		EmoSubstringReturnType e = containsEmoticon(s);
		String ergebnis = "Test-String: " + s;
		if (e != null) {
			String smiley = s.substring(e.offset, e.offset + e.length);
			ergebnis += "\ngefundenes smiley: " + smiley + " => "
					+ toEmoticon(smiley).name();
			ergebnis += "\n[offset = " + e.offset + ", length = " + e.length
					+ "]";
		} else {
			ergebnis += "\nkein smiley gefunden";
		}
		System.out.println();
		System.out.println(ergebnis);

		/*
		 * test icons und drawElement
		 */
		boolean showIcons = true;
		if (showIcons) {
			for (final Emoticon emo : values()) {
				final JFrame frame = new JFrame();
				Dimension size = new Dimension(emo.icon.getWidth(frame),
						emo.icon.getHeight(frame) + 25);
				frame.setSize(size);
				frame.setContentPane(new JPanel() {
					@Override
					protected void paintComponent(Graphics g) {
						super.paintComponent(g);
						Graphics2D g2 = (Graphics2D) g.create();
						g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
								RenderingHints.VALUE_ANTIALIAS_ON);
						g2.drawImage(emo.icon, 0, 0, getWidth(), getHeight(),
								frame);
						emo.drawElement(g2, 0, 0);
						g2.setFont(new Font(Font.DIALOG, Font.BOLD, 30));
						emo.drawElement(g2, 0, getHeight() - 40);
					}
				});
				frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
				frame.setLocationRelativeTo(null);
				frame.setVisible(true);
				while (frame.isShowing()) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
				}
			}
		}

	}
}
